package TWiki::Plugins::FluidPivotsPlugin;
use strict;

use vars qw( $VERSION $RELEASE $SHORTDESCRIPTION $debug $pluginName $NO_PREFS_IN_TOPIC);

$VERSION = '$Rev: 10613 (11 Mar 2007) $';
$RELEASE = 'Dakar';
$SHORTDESCRIPTION = 'FluidPivots';
$pluginName = 'FluidPivotsPlugin';


sub initPlugin 
{
    my ( $topic, $web, $user, $installWeb ) = @_;

    # check for Plugins.pm versions
    if( $TWiki::Plugins::VERSION < 1 ) {
        TWiki::Func::writeWarning( "Version mismatch between $pluginName and Plugins.pm" );
        return 0;
    }

    my $setting = $TWiki::cfg{Plugins}{FluidPivotsPlugin}{FluidPivotsSetting} || 0;
    $debug = $TWiki::cfg{Plugins}{FluidPivotsPlugin}{Debug} || 0;

    &TWiki::Func::writeDebug( "- TWiki::Plugins::FluidPivotsPlugin::initPlugin($web.$topic) is OK" ) if $debug;

    # Plugin correctly initialized
    return 1;
}

sub FluidPivotsPlugin
{
    my ($currentTopic, $currentWeb, $currentTopicContents) = @_;
    my $this = {};
    bless $this;
    $$this{"CURRENT_TOPIC"} = $currentTopic;
    $$this{"CURRENT_WEB"} = $currentWeb;
    $$this{"CURRENT_TOPICONTENTS"} = $currentTopicContents;
    return $this;
}

# Setter for storing the Table object
sub _setTables { my ($this, $table) = @_; $$this{"TABLES"} = $table; }
# Getter for Table object
sub _tables { my ($this) = @_; return $$this{"TABLES"}; }

# Setter for storing the Parameters object
sub _setParameters
{
    my ($this, $args) = @_;
    $$this{"PARAMETERS"} = TWiki::Plugins::ChartPlugin::Parameters->new($args);
}

# Getter for Parameters object
sub _Parameters { my ($this) = @_; return $$this{"PARAMETERS"}; }

# This routine sets the specified web.topic as the location from where to
# get the table information.  If the specified web.topic happen to be the
# same as the web.topic from which the %CHART% was found, then the
# web.topic contents is already part of the ChartPlugin object so there is
# nothing to do.  Otherwise, this routine will read in the specified
# web.topic getting its contents and using that as the source to parse out
# table information.
sub _setTopicContents
{
    my ($this, $inWeb, $inTopic) = @_;
    my $topicContents;
    # If $inWeb and $inTopic match the current web/topic, then we already
    # have the topic contents in the object so there is nothing to do.
    # Otherwise, we need to open the specified web/topic and read in its
    # contents.
    if ( ($inWeb eq $$this{"CURRENT_WEB"}) && ($inTopic eq $$this{"CURRENT_TOPIC"}) ) {
    $topicContents = $$this{"CURRENT_TOPICONTENTS"};
    } else {
    # A difference, so read in the topic.
    (my $meta, $topicContents) = TWiki::Func::readTopic( $inWeb, $inTopic );
    # Check to make sure the web.topic actually exists.  If not, return
    # undef so the caller can catch the error.
    return undef if ($topicContents eq "");
    $topicContents = TWiki::Func::expandCommonVariables($topicContents, $inTopic, $inWeb);
    }

    # Lets parse the specified topic contents looking for tables.
    $this->_setTables(TWiki::Plugins::ChartPlugin::Table->new($topicContents));
    return 1;
}

# This routine returns an red colored error message.
sub _make_error
{
    my ( $msg ) = @_;
    return "<font color=red>FluidPivotsPlugin error: $msg</font>";
}

sub _makePivot
{
    my ( $this, $args, $topic, $web ) = @_;

    $this->_setParameters ($args);

    # See if the parameter 'web' is available.  If not, then default to
    # looking for tables in the current web.
    my $inWeb = $this->_Parameters->getParameter( "web", $web);

    # See if the parameter 'topic' is available.  If not, then default to
    # looking for tables in the current topic.
    my $inTopic = $this->_Parameters->getParameter( "topic", $topic);

    # Before we parse any further parameters, lets get the contents of the
    # specified web/topic.
    if (! $this->_setTopicContents($inWeb, $inTopic)) {
    return _make_error("Error retrieving TWiki topic $inWeb<nop>.$inTopic");
    }

    # See if the parameter 'rows' is available.  This is a required
    # parameter.  If it is missing, then generate an error message.
    my $rows = $this->_Parameters->getParameter( "rows", undef);
    return _make_error("parameter *rows* must be specified") if( ! defined $rows );

    # See if the parameter 'columns' is available.  This is a required
    # parameter.  If it is missing, then generate an error message.
    my $columns = $this->_Parameters->getParameter( "columns", undef);
    return _make_error("parameter *columns* must be specified") if( ! defined $columns );

    # See if the parameter 'data' is available.  This is a required
    # parameter.  If it is missing, then generate an error message.
    my $data = $this->_Parameters->getParameter( "data", undef);
    return _make_error("parameter *data* must be specified") if( ! defined $data );

    # See if the parameter 'operation' is available.
    my $operation = $this->_Parameters->getParameter( "operation", "count");

    # See if the parameter 'name' is available.
    my $name = $this->_Parameters->getParameter( "name", "pivot");

    # Determine which table the user wants to chart
    my $tableName = $this->_Parameters->getParameter( "table", 1);
    # Verify that the table name is valid.
    if (! $this->_tables->checkTableExists($tableName) ) {
    return _make_error("parameter *table* is not valid table; the specified table '$tableName' does not exist.");
    }

    # Build columns range.
    my $colNumber = $this->_tables->getNumColsInTable($tableName);
    my $sourceColRange = "R1:C1..R1:C$colNumber";
    my @sourceColData = $this->_tables->getDataColumns($tableName,$sourceColRange);
    
    # Build rows range.
    my $rowNumber = $this->_tables->getNumRowsInTable($tableName);
    my $sourceDataRange = "R1:C1..R$rowNumber:C$colNumber";
    my @sourceData = $this->_tables->getDataColumns($tableName,$sourceDataRange); 


    # Searching position of col and row data.
    my $posColData = -1;
    my $posRowData = -1;
    my $posData = -1;

    for my $r ( 0..$rowNumber - 1 )
    {
        if ($sourceColData[$r][0] eq $columns)
        {
            $posColData = $r + 1;
        }
        if ($sourceColData[$r][0] eq $rows)
        {
            $posRowData = $r + 1;
        }
        if ($sourceColData[$r][0] eq $data)
        {
            $posData = $r + 1;
        }

    }

    # Get unique elements in row and col data.
    undef my %saw;
    my @uniqRows = $this->_tables->getDataColumns($tableName,"R2:C$posRowData..R$rowNumber:C$posRowData");
    my @uniqRows = grep !$saw{$_}++, @{$uniqRows[0]};
    my %dictRows;
    for my $r ( 0..@uniqRows-1 ){ $dictRows{$uniqRows[$r]} = $r; }
    
    undef my %saw;
    my @uniqCols = $this->_tables->getDataColumns($tableName,"R2:C$posColData..R$rowNumber:C$posColData");
    my @uniqCols = grep !$saw{$_}++, @{$uniqCols[0]};
    my %dictCols;
    for my $r ( 0..@uniqCols-1 ){ $dictCols{$uniqCols[$r]} = $r; }

    # Pivot Table
    my @pivotTable = ();
    for my $r ( 0..@uniqRows+2 )
    {
        for my $c ( 0..@uniqCols+1 )
        {
            $pivotTable[$r][$c]="";
        }
    }

    $pivotTable[0][0] = " *$data - $operation* ";
    $pivotTable[0][1] = " *$columns* ";
    $pivotTable[1][0] = " *$rows* ";

    for my $r ( 0..@uniqRows-1 ) { $pivotTable[$r+2][0] = " *$uniqRows[$r]* ";  }
    for my $c ( 0..@uniqCols-1 ) { $pivotTable[1][$c+1] = " *$uniqCols[$c]* ";  }
    $pivotTable[@uniqRows+2][0] = " *Totals* ";
    $pivotTable[1][@uniqCols+1] = " *Totals* ";

    my @tempPivotTable = ();
    my @totalRows = ();
    my @totalCols = ();
    my @totals = ();

    for my $d ( 1..$rowNumber-1 )
    {
        my $row = $sourceData[$posRowData-1][$d];
        my $r = $dictRows{$row};

        my $col = $sourceData[$posColData-1][$d];
        my $c = $dictCols{$col};
        
        push( @{$tempPivotTable[$r][$c]} , $sourceData[$posData-1][$d]);
        push( @{$totalRows[$r]} , $sourceData[$posData-1][$d]);
        push( @{$totalCols[$c]} , $sourceData[$posData-1][$d]);
        push( @totals , $sourceData[$posData-1][$d]);

    }
    
    
    for my $r ( 0..@uniqRows-1 )
    {
        for my $c ( 0..@uniqCols-1 )
        {  
            $pivotTable[$r + 2][$c +1 ] = $this->doOperation( $operation, @{$tempPivotTable[$r][$c]}) ;
        }

    }

    # Totals
    for my $r ( 0..@uniqRows-1 ) { $pivotTable[$r + 2][@uniqCols+1] = $this->doOperation( $operation, @{$totalRows[$r]} ); }
    for my $c ( 0..@uniqCols-1 ) { $pivotTable[@uniqRows+2][$c +1 ] = $this->doOperation( $operation, @{$totalCols[$c]} ); }
    $pivotTable[@uniqRows+2][@uniqCols+1] = $this->doOperation( $operation, @totals );

    # Build Table.
    my $maxRow = @pivotTable - 1;
    my $tableText = "%TABLE{name=\"$name\" }%\n";
    for my $r ( 0..$maxRow )
    {
        my $maxCol = @{$pivotTable[$r]} - 1;
        $tableText = "$tableText | ";

        for my $c (0..$maxCol)
        {
            $tableText = "$tableText$pivotTable[$r][$c]|";
        }
        $tableText = "$tableText \n";
    }

    return $tableText;
}


sub doOperation
{
    my ( $this, $operation, @list) = @_;

    my $n = @list;


    if ( $n < 1 )
    {
        return " 0 ";
    }
    else
    {
        use Switch;
        switch( $operation )
        {
            case "count"
            {   
                return " $n ";
            }
            case "sum"
            {
                my $val = 0;
                for my $d ( 0..$n-1 ) { $val += $list[$d]; }
                return " $val ";
            }
            case "average"
            {
                my $val = 0;
                for my $d ( 0..$n-1 ) { $val += $list[$d]; }
                $val = $val/$n;
                return " $val ";
            }
            case "max"
            {
                my $val =  $list[0];
                for my $d ( 0..$n-1 ) { $val = $list[$d] if ($list[$d] > $val); }
                return " $val ";
            }
            case "min"
            {
                my $val =  $list[0];
                for my $d ( 0..$n-1 ) { $val = $list[$d] if ($list[$d] < $val); }
                return " $val ";
            }
            case "var"
            {
                my $val = 0;
                my $var = 0;
                
                for my $d ( 0..$n-1 ) { $val += $list[$d]; }
                $val = $val/$n;

                for my $d ( 0..$n-1 ) { $var += ($list[$d] - $val)*($list[$d] - $val); }
                $var = sqrt ($var/$n) ;
                return " $var ";
            }
            else
            {
                return " $n ";
            }
        }
    }
}


sub commonTagsHandler
{
    my $text  = $_[0];
    my $topic = $_[1];
    my $web = $_[2];

    if ( $text !~ m/%PIVOT{.*}%/)
    {
        return;
    }


    require TWiki::Plugins::FluidPivotsPlugin::Parameters;
    require TWiki::Plugins::FluidPivotsPlugin::Table;
    
    my $pivot = FluidPivotsPlugin($topic, $web, $text);
    $text =~ s/%PIVOT{(.*?)}%/$pivot->_makePivot($1, $topic, $web)/eog;

    $_[0] = $text
}

1;
